package main

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"time"
)

func main() {
	args := ParseCommandArgs()

	if args.Exit {
		return
	}

	gp := Gopatcher{
		WorkingDir: args.WorkingDir,
		Filters:    []FileFilter{args.TimeFilter, args.ExtensionFilter},
		DestDir:    args.DestDir,
	}

	gp.List()
	if gp.Files == nil {
		return
	}

	for k, v := range gp.Files {
		fmt.Printf("%v %v\n", v.ModTime().Format("2006-01-02 15:04:05"), strings.TrimPrefix(k, gp.WorkingDir+string(filepath.Separator)))
	}

	dir := gp.Copy()

	zip := gp.Zip(dir)
	fmt.Printf("Output zip file: %v\n", zip)
}

type FileFilter interface {
	Excludes(path string, info os.FileInfo) bool
}

type TimeFilter struct {
	Tm time.Time
}

func (t TimeFilter) Excludes(path string, info os.FileInfo) bool {
	if info.ModTime().Before(t.Tm) {
		return true
	}
	return false
}
func (t TimeFilter) String() string {
	return "TimeFilter: " + t.Tm.String()
}

type ExtensionFilter struct {
	Extensions []string
}

func (e ExtensionFilter) Excludes(path string, info os.FileInfo) bool {
	for _, v := range e.Extensions {
		if strings.HasSuffix(info.Name(), v) {
			return true
		}
	}
	return false
}
func (e ExtensionFilter) String() string {
	return "ExtensionFilter: " + strings.Join(e.Extensions, ",")
}

type CommandArgs struct {
	WorkingDir      string
	TimeFilter      TimeFilter
	ExtensionFilter ExtensionFilter
	DestDir         string
	Exit            bool
}

type Gopatcher struct {
	WorkingDir string
	Filters    []FileFilter
	DestDir    string
	Files      map[string]os.FileInfo
}

func (gp *Gopatcher) List() {
	filepath.Walk(gp.WorkingDir, func(path string, info os.FileInfo, err error) error {
		var ok bool
		switch {
		case info.IsDir():
			ok = false
		case func() bool {
			for _, v := range gp.Filters {
				if v.Excludes(path, info) {
					return true
				}
			}
			return false
		}():
			ok = false
		default:
			ok = true
		}
		if ok {
			if gp.Files == nil {
				gp.Files = make(map[string]os.FileInfo)
			}
			gp.Files[path] = info
		}

		return nil
	})
}

func (gp *Gopatcher) Copy() string {
	target := filepath.Join(gp.DestDir, filepath.Base(gp.WorkingDir))
	for k, v := range gp.Files {
		data, _ := ioutil.ReadFile(k)
		dest := strings.Replace(k, gp.WorkingDir, target, 1)
		os.MkdirAll(filepath.Dir(dest), os.ModePerm.Perm())
		ioutil.WriteFile(dest, data, v.Mode().Perm())
	}
	return target
}

func (gp *Gopatcher) Zip(srcDir string) string {
	buf := &bytes.Buffer{}
	w := zip.NewWriter(buf)
	filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() {
			return nil
		}
		entry := strings.TrimPrefix(path, srcDir+string(filepath.Separator))
		if filepath.Separator == '\\' {
			entry = strings.Replace(entry, "\\", "/", -1)
		}
		f, _ := w.Create(entry)
		data, _ := ioutil.ReadFile(path)
		f.Write(data)
		return nil
	})
	w.Close()

	target := filepath.Join(gp.DestDir, filepath.Base(gp.WorkingDir)+time.Now().Format("20060102150405")+".zip")
	ioutil.WriteFile(target, buf.Bytes(), os.ModePerm.Perm())
	return target
}

func ParseCommandArgs() CommandArgs {
	wd, _ := os.Getwd()
	tm := time.Now().Add(-time.Hour)
	u, _ := user.Current()
	dest := filepath.Join(u.HomeDir, string(filepath.Separator), "Desktop")
	exts := []string{".DS_Store"}
	l := len(os.Args)

	if l > 1 && os.Args[1] == "help" {
		var cmd string
		if os.Getenv("GOOS") == "windows" {
			cmd = "gopatcher.exe"
		} else {
			cmd = "gopatcher"
		}
		fmt.Printf("Usage: %v modtime [exclude extensions] [output dir]\n", cmd)
		exTime := time.Now().Format("2006-01-02T15:04:05")
		fmt.Printf("Examples: %v %v\n", cmd, exTime)
		fmt.Printf("          %v %v %v\n", cmd, exTime, "jpg,doc")
		fmt.Printf("          %v %v %v %v\n", cmd, exTime, "jpg,doc", dest)
		return CommandArgs{
			Exit: true,
		}
	}

	if l > 1 {
		tm, _ = time.ParseInLocation("2006-01-02T15:04:05", os.Args[1], time.Local)
		fmt.Printf("Args - time: %v\n", tm)
	}
	if l > 2 {
		for _, v := range strings.Split(os.Args[2], ",") {
			exts = append(exts, v)
		}
		fmt.Printf("Args - extensions: %v\n", exts)
	}
	if l > 3 {
		dest = os.Args[3]
		fmt.Printf("Args - directory: %v\n", dest)
	}
	timeFilter := TimeFilter{tm}
	extensionFilter := ExtensionFilter{exts}
	return CommandArgs{wd, timeFilter, extensionFilter, dest, false}
}
